package com.hfstu.framework.core.orm.hibernate;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;

import com.hfstu.framework.model.BakDeleteModel;
import com.hfstu.framework.model.BaseModel;
import com.hfstu.framework.util.StringUtil;
import com.hfstu.framework.uuid.UUIDGenerator;

@SuppressWarnings("all")
public abstract class MyHibernateDaoSupport<T extends BaseModel, PK extends Serializable> {
	@Resource
	protected SessionFactory sessionFactory;

	public void clear() {
		getSession().clear();
	}

	public int count() {
		List list = getSession().createQuery(
				new StringBuilder().append("select count(*) from ")
						.append(getEntityName()).toString()).list();

		return Integer.parseInt(((Long) list.get(0)).toString());
	}

	public Criteria createCriteria(final Criterion... criterions) {

		Criteria criteria = getSession().createCriteria(getEntityName());
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;

	}

	/**
	 * 根据查询HQL与参数列表创建Query对象. 与find()函数可进行更加灵活的操作.
	 * 
	 * @param values
	 *            命名参数,按名称绑定.
	 */
	public Query createQuery(final String hql, final Map<String, ?> values) {
		Assert.hasText(hql, "hql不能为空");
		Query query = getSession().createQuery(hql);
		if (values != null) {
			query.setProperties(values);
		}
		return query;
	}

	public Query createQuery(final String hql, final Object... values) {
		Assert.hasText(hql, "hql不能为空");
		Query queryObject = getSession().createQuery(hql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				queryObject.setParameter(i, values[i]);
			}
		}
		return queryObject;
	}

	public Query createSQLQuery(final String sql, final Object... values) {
		Assert.hasText(sql, "sql不能为空");
		Query queryObject = getSession().createSQLQuery(sql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				queryObject.setParameter(i, values[i]);
			}
		}
		return queryObject;
	}

	public Query createSQLQuery(final String sql, final Map<String, ?> values) {
		Assert.hasText(sql, "sql不能为空");
		Query query = getSession().createSQLQuery(sql);
		if (values != null) {
			query.setProperties(values);
		}
		return query;
	}

	public void deleteAllEntities() {
		getSession().createQuery(
				new StringBuilder().append("delete from ")
						.append(getEntityName()).toString()).executeUpdate();
	}

	public void deleteAllEntities(Collection<T> entities) {
		for (T t : entities) {
			getSession().delete(t);
		}
	}

	public void deleteEntity(PK id) {
		T entity = getEntityById(id);
		if (entity != null) {
			if (entity instanceof BakDeleteModel) {
				((BakDeleteModel) entity).setBeenDeleted(1);
				((BakDeleteModel) entity).setDeletedTime(new Date());
				getSession().update(entity);
			} else {
				getSession().delete(entity);
			}

		}
	}

	public void deleteEntity(T entity) {
		deleteEntity((PK) entity.getId());
	}

	public void deleteEntityByPropertys(String[] propertyNames, Object[] values) {
		if ((ArrayUtils.isEmpty(propertyNames)) || (ArrayUtils.isEmpty(values))
				|| (propertyNames.length != values.length)) {
			throw new IllegalArgumentException("arguments is invalid.");
		}
		StringBuffer hsql = new StringBuffer("delete from ").append(
				getEntityName()).append(" where 1=1");
		for (String propertyName : propertyNames) {
			hsql.append(" and ").append(propertyName).append(" = ? ");
		}
		Query query = getSession().createQuery(hsql.toString());
		for (int i = 0; i < values.length; i++) {
			query.setParameter(i, values[i]);
		}
		query.executeUpdate();
	}

	/**
	 * 为Criteria添加distinct transformer. 预加载关联对象的HQL会引起主对象重复, 需要进行distinct处理.
	 */
	public Criteria distinct(Criteria criteria) {
		criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
		return criteria;
	}

	/**
	 * 为Query添加distinct transformer. 预加载关联对象的HQL会引起主对象重复, 需要进行distinct处理.
	 */
	public Query distinct(Query query) {
		query.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
		return query;
	}

	public void executeSql(final String executeSql) {
		Query query = getSession().createSQLQuery(executeSql);
		query.executeUpdate();
	}

	/**
	 * 按Criteria查询对象列表.
	 * 
	 * @param criterions
	 *            数量可变的Criterion.
	 */
	public List<T> findListByCriteria(final Criterion... criterions) {
		Criteria criteria = this.createCriteria(criterions);
		return criteria.list();
	}

	public List<T> findListByCriteria(final DetachedCriteria detachedCriteria) {
		Criteria criteria = detachedCriteria.getExecutableCriteria(getSession());
		return criteria.list();
	}
	public List findListByQuery(final String hql) {
		Query query = getSession().createQuery(hql);
		return query.list();
	}

	public List findListByQuery(final String hql, final Object... values) {
		Query query = createQuery(hql, values);
		return query.list();
	}

	public List findListBySQLQuery(final String hql, final Object... values) {
		Query query = createSQLQuery(hql, values);
		return query.list();
	}

	public List findListByQueryPaging(final String hql, final int rowStart,
			final int rowEnd) {
		int pageSize = rowEnd - rowStart;
		Query query = getSession().createQuery(hql);
		if (pageSize > 0 && rowStart >= 0) {
			query.setFirstResult(rowStart).setMaxResults(pageSize);
		}

		return query.list();
	}

	public List findListBySQLQueryPaging(final String sql, final int rowStart,
			final int rowEnd) {
		int pageSize = rowEnd - rowStart;
		Query query = getSession().createSQLQuery(sql);
		if (pageSize > 0 && rowStart >= 0) {
			query.setFirstResult(rowStart).setMaxResults(pageSize);
		}

		return query.list();
	}

	public Long findTotalCountByQuery(final String hql) {
		return (Long) getSession().createQuery(hql).uniqueResult();
	}

	/**
	 * 按HQL查询对象列表.
	 * 
	 * @param values
	 *            数量可变的参数,按顺序绑定.
	 */
	public <X> List<X> find(final String hql, final Object... values) {
		return createQuery(hql, values).list();
	}

	/**
	 * 按HQL查询对象列表.
	 * 
	 * @param values
	 *            命名参数,按名称绑定.
	 */
	public <X> List<X> find(final String hql, final Map<String, ?> values) {
		return createQuery(hql, values).list();
	}

	/**
	 * 按Criteria查询唯一对象.
	 * 
	 * @param criterions
	 *            数量可变的Criterion.
	 */
	@SuppressWarnings("unchecked")
	public T findUnique(final Criterion... criterions) {
		return (T) createCriteria(criterions).uniqueResult();
	}

	public T findUnique(final DetachedCriteria detachedCriteria) {
		Criteria criteria = detachedCriteria.getExecutableCriteria(getSession());
		return (T)criteria.uniqueResult();
	}
	
	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values
	 *            数量可变的参数,按顺序绑定.
	 */
	public <X> X findUnique(final String hql, final Object... values) {
		return (X) createQuery(hql, values).uniqueResult();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values
	 *            命名参数,按名称绑定.
	 */
	public <X> X findSQLUnique(final String sql, final Map<String, ?> values) {
		return (X) createSQLQuery(sql, values).uniqueResult();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values
	 *            数量可变的参数,按顺序绑定.
	 */
	public <X> X findSQLUnique(final String sql, final Object... values) {
		return (X) createSQLQuery(sql, values).uniqueResult();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values
	 *            命名参数,按名称绑定.
	 */
	public <X> X findUnique(final String hql, final Map<String, ?> values) {
		return (X) createQuery(hql, values).uniqueResult();
	}

	/**
	 * 按属性查找唯一对象, 匹配方式为相等.
	 */
	@SuppressWarnings("unchecked")
	public T findUniqueBy(final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return (T) createCriteria(criterion).uniqueResult();
	}

	public void flush() {
		getSession().flush();
	}

	/**
	 * 按id列表获取对象列表.
	 */
	public List<T> get(final Collection<PK> ids) {
		return findListByCriteria(Restrictions.in(getIdName(), ids));
	}

	@SuppressWarnings("unchecked")
	public List<T> getAllEntities() {
		return getSession().createQuery(
				new StringBuilder().append("FROM ").append(getEntityName())
						.toString()).list();
	}

	@SuppressWarnings("unchecked")
	public T getEntityById(PK id) {
		return (T) getSession().get(getEntityName(), id);
	}

	protected abstract String getEntityName();

	
	/**
	 * 取得对象的主键名.
	 */
	public String getIdName() {
		ClassMetadata meta = getSessionFactory().getClassMetadata(
				getEntityName());
		return meta.getIdentifierPropertyName();
	}

	/**
	 * 初始化对象. 使用load()方法得到的仅是对象Proxy, 在传到View层前需要进行初始化. 如果传入entity,
	 * 则只初始化entity的直接属性,但不会初始化延迟加载的关联集合和属性. 如需初始化关联属性,需执行:
	 * Hibernate.initialize(user.getRoles())，初始化User的直接属性和关联集合.
	 * Hibernate.initialize
	 * (user.getDescription())，初始化User的直接属性和延迟加载的Description属性.
	 */
	public void initProxyObject(Object proxy) {
		Hibernate.initialize(proxy);
	}

	/**
	 * 判断对象的属性值在数据库内是否唯一.
	 * 
	 * 在修改对象的情景下,如果属性新修改的值(value)等于属性原来的值(orgValue)则不作比较.
	 */
	public boolean isPropertyUnique(final String propertyName,
			final Object newValue, final Object oldValue) {
		if (newValue == null || newValue.equals(oldValue)) {
			return true;
		}
		Object object = findUniqueBy(propertyName, newValue);
		return (object == null);
	}

	public void merge(T entity) {
		this.repairEntity(entity);
		getSession().merge(entity);
	}

	public List querySql(final String querySql) {
		Query query = getSession().createSQLQuery(querySql);
		return query.list();
	}

	private void repairEntities(Collection<T> entities) {
		for (T t : entities) {
			this.repairEntity(t);
		}
	}

	private void repairEntity(T entity) {
		if (StringUtil.isNullOrEmpty(entity.getId())) {
			entity.setId(UUIDGenerator.getUUID());
		}
	}

	public void saveEntities(final List<T> entities) {
		Session session = getSession();
		T entity = null;
		for (int i = 0; i < entities.size(); i++) {
			// saveEntity(t);
			entity = entities.get(i);
			this.repairEntity(entity);
			entity.setCreateTime(new Date());
			entity.setLastChangeTime(new Date());
			session.save(entity);

			if (i % 20 == 0) { // 单次批量操作的数目为20
				session.flush(); // 清理缓存，执行批量插入20条记录的SQL insert语句
				session.clear(); // 清空缓存中的Customer对象
			}
		}
	}

	public T saveEntity(T entity) {
		this.repairEntity(entity);
		entity.setCreateTime(new Date());
		entity.setLastChangeTime(new Date());
		getSession().save(entity);
		return entity;
	}

	public T updateEntity(T entity) {
		entity.setLastChangeTime(new Date());
		getSession().update(entity);
		return entity;
	}

	public void saveOrUpdateAllEntities(Collection<T> entities) {
		this.repairEntities(entities);
		for (T t : entities) {
			saveOrUpdateEntity(t);
		}

	}

	@SuppressWarnings("unchecked")
	public T saveOrUpdateEntity(T entity) {
		this.repairEntity(entity);
		T ref = getEntityById((PK) entity.getId());
		if (ref == null) {
			saveEntity(entity);
			return entity;
		} else {
			BeanUtils.copyProperties(entity, ref);
			updateEntity(ref);
			return ref;
		}

	}

	public void executeUpdate(String hql) {
		getSession().createQuery(hql).executeUpdate();
	}

	/**
	 * 取得当前Session.
	 */
	public Session getSession() {
		return sessionFactory.getCurrentSession();
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

}
